package ec_si_template;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;

import org.jfree.ui.RefineryUtilities;

import cec2010suite.F0;
import cec2010suite.F1;
import cec2010suite.F10;
import cec2010suite.F11;
import cec2010suite.F12;
import cec2010suite.F13;
import cec2010suite.F14;
import cec2010suite.F15;
import cec2010suite.F16;
import cec2010suite.F17;
import cec2010suite.F18;
import cec2010suite.F19;
import cec2010suite.F2;
import cec2010suite.F20;
import cec2010suite.F3;
import cec2010suite.F4;
import cec2010suite.F5;
import cec2010suite.F6;
import cec2010suite.F7;
import cec2010suite.F8;
import cec2010suite.F9;
import cec2010suite.Function;

/**
 * MAIN: See also README.TXT 
 * 
 * Basic template for implementing an optimization algorithm.
 *  
 * Start here to include your own algorithm into this template
 * 
 * @author Andreas
 * 
 */
public class OptimizationAlgorithm {
	public static void optimizeAJ(Function f) {        
	        
		// Size of the swarm / population
		final int nPop = Config.nPop;

		// Maximum number of function evaluations
		final long max_evaluations = Config.max_evaluations;
				
		// Problem dimension
		final int dim = f.getDimension();
		
		// Console output
		final int ouputLevel = Config.ouputLevel;

		// if set to true the position of individuals in each iteration are
		// written to file "fileName" - see below
		final boolean printToFile = Config.printToFile;

		DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HH'h'mm'm'ss's'");
		Date date = new Date();
		String fileName = "output" + dateFormat.format(date) + ".txt";

		// Obtain the lower and upper bound of the search space.
		// This boundary is the same for all dimensions.
		final double minBound = f.getMin();
		final double maxBound = f.getMax();

		// Set the lower and upper bound of the initialization range.
		// this will initialize the swarm over the whole search space
		final double maxInit = f.getMax();
		final double minInit = f.getMin();
		// In many papers the swarm / population is initialized in an area far
		// away from the local optimum
		// E.g. In order to initialize the swarm in one corner of the search
		// space use this
		// maxInit = f.getMax();
		// minInit = maxInit/2;

		// Array containing the objects of individuals
		Individual[] ind = new Individual[nPop]; // allocation

		// counter for number of fitness evaluations
		long fitness_evaluations = 0;

		// output information
		System.out.println(f.getShortName() + "/" + f.getFullName()
				+ " dimensions=" + f.getDimension() + " optimum="
				+ f.compute(f.getOptimum()));

		// get start time in milliseconds
		long start = System.currentTimeMillis();

		// ----------------------------------------------------------------------------------
		// __________________________________________________________________________________
		// INITIALIZATION
		// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

		// for each individual: perform initialization
		for (int i = 0; i < nPop; i++) {
			ind[i] = new Individual(i, f, dim, minInit, maxInit, minBound,
					maxBound);
		}
		/**
		 * updating the neighbour list, so each individual will know its neighbors.*/
		for (int i = 0; i < nPop; i++) {
			ind[i].setNeighbours(ind);
		}

		// update fitness evaluation count - nPop evaluations already performed
		// during initialization
		fitness_evaluations = nPop;

		// __________________________________________________________________________________
		// END INITIALIZATION
		// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
		// ----------------------------------------------------------------------------------

		// --- OUTPUT
		// depending on outputLevel, print current position
		if (ouputLevel > 1) {
			outputPositions(ind);
		}
		System.out.println("Finished initialization\n\n");
		// --- OUTPUT

		// --- PRINT TO FILE
		// initialize MyFileWriter
		MyFileWriter mfw = null;
		if (printToFile) {
			mfw = new MyFileWriter(fileName);
		}
		// --- PRINT TO FILE
		
		// calculate the maximum number of iterations in order to allocate the the array fitnessHistrory
		int max_iters = (int)Math.round(max_evaluations / nPop)+1;
		// save the fitness per iteration in this array
		double[] fitnessHistory = new double[max_iters];
		// sort individuals by fitness
		Arrays.sort(ind, new ComparatorByBestFitness());
		// update fitnessHistroy array
		fitnessHistory[0] = ind[0].getfitness();
		// sort individuals by index
		Arrays.sort(ind, new ComparatorByIndex());
		
		System.out.println("max_iters " + max_iters);
		System.out.println("Starting loop ");

		// ----------------------------------------------------------------------------------
		// __________________________________________________________________________________
		// LOOP
		// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
		boolean runLoop = true; // perform loop as long as runLoop == true
		int t = 1; // counter for iterations

		while (runLoop) {

			// check if max number of evaluation is reached and break if yes
			if (fitness_evaluations >= max_evaluations) {
				System.out.println("Reached " + max_evaluations
						+ " function evaluations... forcing to leave loop");
				runLoop = false;
				break;
			}

			// --- PRINT TO FILE
			if (printToFile) {
				mfw.append("%Iteration" + t + "\n");
			}// --- PRINT TO FILE

			// FOR ALL INDIVIDUALS
			/**in this algorithm, there is a step where all the fitness are evaluated, BEFORE moving.
			 * So, in the code, the first FOR, is for this fitness update
			 * the second one is for position update.
			 * */
			for (Individual i : ind) {
				i.UpdateLuciferin();
			}
			
			for (Individual i : ind) {
				// update the individual

				i.update(dim, minBound, maxBound);
				
				// increase number of fitness evaluations
				fitness_evaluations++;
				// print positions of all individuals in this current iteration
				// to a file
				if (printToFile) {
					mfw.append(Arrays.toString(i.getp()) + "\n");
				}
			} // all individuals are updated now

			// --- OUTPUT
			// sort array "ind" by best fitness and output best fitness found so
			// far
			Arrays.sort(ind, new ComparatorByBestFitness());

			// depending on outputLevel, print best fitness
			if (ouputLevel > 0) {
				System.out
						.format("After iteration %06d ... that equals %08d fitness evaluations. Best fitness: %15.10f%n ",
								t, fitness_evaluations, ind[0].getfitness());
			}
			// update fitnessHistroy array
			fitnessHistory[t] = ind[0].getfitness();
			// sort array "ind" by index in order to keep the order of
			// individuals consistent.
			Arrays.sort(ind, new ComparatorByIndex());

			// depending on outputLevel, print current position
			if (ouputLevel > 1) {
				outputPositions(ind);
			}
			// --- OUTPUT

			// increase iteration counter
			t++;
		}
		// __________________________________________________________________________________
		// END LOOP
		// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
		// ----------------------------------------------------------------------------------

		Arrays.sort(ind, new ComparatorByBestFitness());
		System.out.println("\nBest result after " + fitness_evaluations
				+ "fitness_evaluations:" + ind[0].getfitness());
		// depending on outputLevel, print current position
		if (ouputLevel > 1) {
			outputPositions(ind);
		}

		// get end time in milliseconds
		long end = System.currentTimeMillis();
		// output execution time
		System.out.println("Execution time was " + (end - start) + " ms.");

		String hlp = (printToFile) ? "ON - THIS SLOWS DOWN RUNTIME!!!" : "off";
		System.out.println("Wrinting to file is turned " + hlp);
		
		// plot the fitnesshistory		
		if (Config.plotFitnessHistory) {
			final PlotFitness demo = new PlotFitness(fitnessHistory);
			demo.pack();
			RefineryUtilities.centerFrameOnScreen(demo);
			demo.setVisible(true);
		}
	        
	}

	/**
	 * print position of the individuals on the console
	 * 
	 * @param ind
	 *            the swarm / population
	 */
	private static void outputPositions(Individual[] ind) {
		for (Individual i : ind) {
			System.out.print("index " + i.getindex());
			System.out.print(" fitness= " + i.getfitness());
			System.out.println(" ... p=" + Arrays.toString(i.getp()));
		}

	}

	/**
	 * simple template for EC or SI algorithm. uses the cec2012 template for
	 * function evaluation
	 * 
	 * @param params
	 */
	public static final void main(final String[] params) {
		// add functions to a LinkedList and evaluate them
		LinkedList<Function> functions = new LinkedList<Function>();

		//functions.add(new F1());
		// functions.add(new F2());
		// functions.add(new F3());
		//
		// //
		// functions.add(new F4());
		// functions.add(new F5());
		// functions.add(new F6());
		// functions.add(new F7());
		// functions.add(new F8());
		//
		// //
		// functions.add(new F9());
		// functions.add(new F10());
		// functions.add(new F11());
		// functions.add(new F12());
		// functions.add(new F13());
		//
		// //
		// functions.add(new F14());
		// functions.add(new F15());
		// functions.add(new F16());
		// functions.add(new F17());
		// functions.add(new F18());
		//
		// //
		// functions.add(new F19());
		 functions.add(new F20());

		for (Function fun : functions) {
			OptimizationAlgorithm.optimizeAJ(fun);
		}
	}
}
